// 
//  Author:
//    Snowdrop Short snowdrop dot short at gmail dot com
// 
//  Copyright (c) 2009, Snowdrop Short
// 
//  All rights reserved.
// 
//  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
// 
//     * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in
//       the documentation and/or other materials provided with the distribution.
//     * Neither the name of the [ORGANIZATION] nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
// 
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
//  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
//  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
//  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
//  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
//  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
//  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
//  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
//  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
//  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// 

using System;
using System.IO;
using System.IO.Compression;
using System.Threading;
using System.Drawing;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using OpenMetaverse;
using OpenMetaverse.Http;
using OpenMetaverse.Imaging;

namespace OpenFuse.Backend.Commands
{
	public class UploadObjectCommand : Command
	{
		
		Cache.Asset _asset;
		
		// Map between the 'stale' assets, loaded from the cache, and the corresponding items
		// uploaded and live on the grid
		Dictionary<UUID, InventoryItem> _assetRemapping = new Dictionary<UUID, InventoryItem>();
		
		struct OarInfo
		{
			public AssetType Type;
			public byte[] Data;
		}
		
		Dictionary<UUID, OarInfo> _oarCache = new Dictionary<UUID, OarInfo>();
		
		public UploadObjectCommand(SlSession session, Cache.Asset asset) : base(session)
		{
			_asset = asset;
		}
		
		public override void Run()
		{
			LinkSet staleLinkSet = null;
			
			if(_asset.Data == null)
				_session.Logger.Debug("No data contents in file");
			
			MemoryStream memStream = new MemoryStream(_asset.Data);

			// Read the Oar archive stored in the Data section of the file
			TarArchiveReader archive = new TarArchiveReader(new GZipStream(memStream, CompressionMode.Decompress));
            TarArchiveReader.TarEntryType entryType;
			byte[] data;
			string filePath;
			
			while ((data = archive.ReadEntry(out filePath, out entryType)) != null)
            {
                _session.Logger.DebugFormat("Successfully read {0} {1} bytes", filePath, data.Length);
                
                if (TarArchiveReader.TarEntryType.TYPE_DIRECTORY == entryType)
                {
                    _session.Logger.InfoFormat("Ignoring directory entry {0}", filePath);
                }
                else if(filePath.StartsWith(ArchiveConstants.OBJECTS_PATH))
                {
					staleLinkSet = new LinkSet(_session, data);
                }
                else if(filePath.StartsWith(ArchiveConstants.ASSETS_PATH))
                {
					UUID assetUUID = OarFile.GrabUuidFromFilename(filePath);
					AssetType assetType = OarFile.GrabAssetTypeFromFilename(filePath);

					OarInfo oi = new OarInfo();
					oi.Data = data;
					oi.Type = assetType;
					_oarCache.Add(assetUUID, oi);
                }
			}

			_session.Logger.DebugFormat("Starting upload of '{0}'", staleLinkSet.Root.Properties.Name);

			// upload all 'simple' assets, scripts, immages, wearbles, etc.
			// but not Notecards and Objects, as they can contain embedded objects
			foreach(KeyValuePair<UUID, OarInfo> kv in _oarCache)
			{
				if(kv.Key != UUID.Zero) {
					// check if the asset is part of a task inventory in one of the prims
					InventoryItem item = staleLinkSet.FindInventoryItemByAssetID(kv.Key);
					if(item != null) {
						UUID newItemId = UUID.Zero;
						
						if(kv.Value.Type == AssetType.Texture)
							newItemId = UploadImage(_session, kv.Value.Data, true, item);
						else if(kv.Value.Type == AssetType.Clothing)
							newItemId = UploadClothing(_session, kv.Key, kv.Value.Data, item);
						else if(kv.Value.Type == AssetType.Bodypart)
							newItemId = UploadBodypart(_session, kv.Key, kv.Value.Data, item);
						else if(kv.Value.Type == AssetType.LSLText || kv.Value.Type == AssetType.LSLBytecode)
							newItemId = UploadNotecard(_session, kv.Key, kv.Value.Data, item);
						else if(kv.Value.Type == AssetType.Object || kv.Value.Type == AssetType.Notecard || kv.Value.Type == AssetType.Gesture)
							break;
						else
							_session.Logger.WarnFormat("Attempt to upload unsupported asset type:{0}", kv.Value.Type.ToString());
						
						// save the new item id, so we can upload the item into the task folder
						if(newItemId != UUID.Zero)
							_assetRemapping[kv.Key] = (InventoryItem)_session.GridClient.Inventory.FetchItem(newItemId, _session.AvatarId, 4000);
					}
				}
			}
			
			UploadObject(staleLinkSet);
			_session.Logger.DebugFormat("Completed upload of '{0}'", staleLinkSet.Root.Properties.Name);
		}
		
		UUID UploadObject(LinkSet staleLinkSet)
		{
			UUID objectInventoryID = UUID.Zero;
			
			// Start performing the actual upload of the linkset
			// this is done by recursing down the tree of objects
			// in a depth first, post process manner
			foreach(BackendPrimitive be in staleLinkSet.Children)
			{
				foreach(InventoryItem item in be.Inventory)
				{
					// locate the already loaded XML definition of the object
					OarInfo objectData;
					if(_oarCache.TryGetValue(item.AssetUUID, out objectData))
					{
						if(objectData.Type == AssetType.Object)
						{
							_session.Logger.DebugFormat("Uploading child '{0}':{1}", item.Name, item.AssetUUID);
							LinkSet staleSubLinkSet = new LinkSet(_session, objectData.Data);
							objectInventoryID = UploadObject(staleSubLinkSet);

							if(objectInventoryID == UUID.Zero)
								_session.Logger.ErrorFormat("Failed to upload '{0}'", staleSubLinkSet.Root.Properties.Name);
							
							_assetRemapping[staleSubLinkSet.Root.ID] = (InventoryItem)_session.GridClient.Inventory.FetchItem(objectInventoryID, _session.AvatarId, 4000);
							_session.Logger.DebugFormat("Uploaded child '{0}' : {1} stale ID={2}", item.Name, item.AssetUUID, staleSubLinkSet.Root.ID);
						}
					}
					else
						_session.Logger.ErrorFormat("Failed to find definition for '{0}' with id {1}", item.Name, item.AssetUUID);
				}
			}
			objectInventoryID = UploadLinkset(staleLinkSet, DeRezDestination.AgentInventoryTake, _session.WorkFolderID);
			return objectInventoryID;
		}
		

 		static UUID UploadNotecard(SlSession session, UUID assetUUID, byte[] data, InventoryItem item)
		{
			UUID uploadedItemId = UUID.Zero;
			AutoResetEvent waiter = new AutoResetEvent(false);
			
			OpenMetaverse.Assets.AssetNotecard notecard = new OpenMetaverse.Assets.AssetNotecard(assetUUID, data);
			notecard.Decode();
			session.Logger.DebugFormat("Uploading notecard {0}", assetUUID);
			
			// we have to rely on the old UDP system, since the Caps based system does
			// not provide a means to specify the Wearable type
			UUID transactionID = UUID.Random();
			
			EventHandler<AssetUploadEventArgs> onAssetUploaded = delegate(object o, AssetUploadEventArgs e)
				{
					if(e.Upload.Success)
						session.Logger.DebugFormat("Successfully uploaded {0}", e.Upload.AssetID);
					else
						session.Logger.ErrorFormat("Failed to upload {0}", e.Upload.AssetID);
					waiter.Set();
				};

			session.GridClient.Assets.AssetUploaded += onAssetUploaded;
			session.GridClient.Assets.RequestUpload(out uploadedItemId, notecard.AssetType, data, false, transactionID);
			waiter.WaitOne(10000);
			session.GridClient.Assets.AssetUploaded -= onAssetUploaded;

			// TODO .. assign proper name to inventory item.
			session.GridClient.Inventory.RequestCreateItem(session.WorkFolderID,
			                                               item.Name,
			                                               item.Description,
			                                               notecard.AssetType,
			                                               transactionID,
			                                               InventoryType.Notecard,
			                                               item.Permissions.NextOwnerMask,
			                                               delegate(bool success, InventoryItem uploadedItem)
			                                               {
																if(success)
																	uploadedItemId = uploadedItem.UUID;
																else
																	session.Logger.ErrorFormat("Error creating inventory Item for '{0}'", notecard.AssetData);
																waiter.Set();
															}
														);
			// Wait for the upload to complete			                                                
			if(!waiter.WaitOne(50000))
				session.Logger.ErrorFormat("Failed to upload clothing '{0}'", notecard.AssetData);
			
			return uploadedItemId;
		}
		
 		static UUID UploadBodypart(SlSession session, UUID assetUUID, byte[] data, InventoryItem item)
		{
			UUID uploadedItemId = UUID.Zero;
			AutoResetEvent waiter = new AutoResetEvent(false);
			
			OpenMetaverse.Assets.AssetBodypart bodypart = new OpenMetaverse.Assets.AssetBodypart(assetUUID, data);
			bodypart.Decode();
			session.Logger.DebugFormat("Uploading clothing {0}", bodypart.Name);
			
			// we have to rely on the old UDP system, since the Caps based system does
			// not provide a means to specify the Wearable type
			UUID transactionID = UUID.Random();
			
			EventHandler<AssetUploadEventArgs> onAssetUploaded = delegate(object o, AssetUploadEventArgs e)
				{
					if(e.Upload.Success)
						session.Logger.DebugFormat("Successfully uploaded {0}", e.Upload.AssetID);
					else
						session.Logger.ErrorFormat("Failed to upload {0}", e.Upload.AssetID);
					waiter.Set();
				};

			session.GridClient.Assets.AssetUploaded += onAssetUploaded;
			session.GridClient.Assets.RequestUpload(out uploadedItemId, bodypart.AssetType, data, false, transactionID);
			waiter.WaitOne(10000);
			session.GridClient.Assets.AssetUploaded -= onAssetUploaded;
			session.GridClient.Inventory.RequestCreateItem(session.WorkFolderID,
			                                               item.Name,
			                                               item.Description,
			                                               bodypart.AssetType,
			                                               transactionID,
			                                               InventoryType.Wearable,
			                                               bodypart.WearableType,
			                                               bodypart.Permissions.NextOwnerMask,
			                                               delegate(bool success, InventoryItem uploadedItem)
			                                               {
																if(success)
																	uploadedItemId = uploadedItem.UUID;
																else
																	session.Logger.ErrorFormat("Error creating inventory Item for '{0}'", bodypart.Name);
																waiter.Set();
															}
														);
			// Wait for the upload to complete			                                                
			if(!waiter.WaitOne(50000))
				session.Logger.ErrorFormat("Failed to upload clothing '{0}'", bodypart.Name);
			
			return uploadedItemId;
		}

		static UUID UploadClothing(SlSession session, UUID assetUUID, byte[] data, InventoryItem item)
		{
			UUID uploadedItemId = UUID.Zero;
			AutoResetEvent waiter = new AutoResetEvent(false);
			
			OpenMetaverse.Assets.AssetClothing clothing = new OpenMetaverse.Assets.AssetClothing(assetUUID, data);
			clothing.Decode();
			session.Logger.DebugFormat("Uploading clothing {0}", clothing.Name);
			
			// we have to rely on the old UDP system, since the Caps based system does
			// not provide a means to specify the Wearable type
			UUID transactionID = UUID.Random();
			
			EventHandler<AssetUploadEventArgs> onAssetUploaded = delegate(object o, AssetUploadEventArgs e)
				{
					if(e.Upload.Success)
						session.Logger.DebugFormat("Successfully uploaded {0}", e.Upload.AssetID);
					else
						session.Logger.ErrorFormat("Failed to upload {0}", e.Upload.AssetID);
					waiter.Set();
				};

			session.GridClient.Assets.AssetUploaded += onAssetUploaded;
			session.GridClient.Assets.RequestUpload(out uploadedItemId, clothing.AssetType, data, false, transactionID);
			waiter.WaitOne(10000);
			session.GridClient.Assets.AssetUploaded -= onAssetUploaded;
			session.GridClient.Inventory.RequestCreateItem(session.WorkFolderID,
			                                               item.Name,
			                                               item.Description,
			                                               clothing.AssetType,
			                                               transactionID,
			                                               InventoryType.Wearable,
			                                               clothing.WearableType,
			                                               clothing.Permissions.NextOwnerMask,
			                                               delegate(bool success, InventoryItem uploadedItem)
			                                               {
																if(success)
																	uploadedItemId = uploadedItem.UUID;
																else
																	session.Logger.ErrorFormat("Error creating inventory Item for '{0}'", clothing.Name);
																waiter.Set();
															}
														);
			// Wait for the upload to complete			                                                
			if(!waiter.WaitOne(50000))
				session.Logger.ErrorFormat("Failed to upload clothing '{0}'", clothing.Name);
			
			return uploadedItemId;
		}
		
		                        
		static UUID UploadTexture(SlSession session, UUID assetUUID, byte[] data)
		{
			// first determine if the asset already exists on the grid, use a high discardlevel, to reduce 
			// bandwidth consumption
			byte[] existingImage = Utilities.DownloadTexture(session, assetUUID, 4);
			if(existingImage != null) 
			{
				session.Logger.Debug("Texture already exists on the grid");
				return assetUUID;
			}
			
			// if the asset doesn't exist, then we have to upload it
			// note, this will generate a new asset id
			session.Logger.Debug("Texture not found on server, uploading");
			return UploadImage(session, data, true, null);
		}

        static UUID UploadImage(SlSession session, byte[] data, bool lossless, InventoryItem item)
        {
            UUID uploadedItemId = UUID.Zero;

			//TODO Find a propper item name
            AutoResetEvent uploadEvent = new AutoResetEvent(false);
            session.GridClient.Inventory.RequestCreateItemFromAsset(data, item.Name,
                item.Description, AssetType.Texture, item.InventoryType, session.WorkFolderID, 

                delegate(bool success, string status, UUID itemID, UUID assetID)
                {
                    if (success)
                    {
                        uploadedItemId = itemID;
                    }
                    else
                    {
                        Console.WriteLine("Failed to upload image: " + status);
                    }

                    uploadEvent.Set();
                }
            );

            // The images are small, 60 seconds should be plenty
            uploadEvent.WaitOne(1000 * 60, false);

            return uploadedItemId;
        }

		private enum ImporterState { None, RezzingParent, RezzingChildren, Linking, Idle};

		public UUID UploadLinkset(LinkSet staleLinkSet, DeRezDestination destinationType, UUID destinationFolder)
        {
			// map between the 'stale' prmitives, loaded from the cache, but not exisiting on the grid
			// and the 'live' primitives, created on the grid
			Dictionary<UUID, Primitive> primitiveRemapping = new Dictionary<UUID, Primitive>();
			
            List<Primitive> primsCreated = new List<Primitive>();
			ImporterState state = ImporterState.None;
			uint rootLocalID = 0;
            Vector3 currentPosition = new Vector3(0.0f);
			Primitive currentPrim = null;
			AutoResetEvent primDone = new AutoResetEvent(false);
			List<uint> linkQueue = null;
			LinkSet liveLinkSet = null;							// linkset we're creating as a result of uploading
			
#region Locally defined delegates
			// Declare delegates
			EventHandler<PrimEventArgs> OnNewPrimCallback = delegate(object o, PrimEventArgs e)
		        {
					// OpenSim does not reliably set the "CreateSelected" flag during linking
					if(state != ImporterState.Linking && (e.Prim.Flags & PrimFlags.CreateSelected) == 0)
					{
						//_session.Logger.DebugFormat("Got irrelevant prim.LocalID={0}", prim.LocalID);
		                return; // We received an update for an object we didn't create
					}
		
		            switch (state)
		            {
		                case ImporterState.RezzingParent:
		                    rootLocalID = e.Prim.LocalID;
		                    goto case ImporterState.RezzingChildren;
		                case ImporterState.RezzingChildren:
		                    if (!primsCreated.Contains(e.Prim))
		                    {
								_session.Logger.DebugFormat("Mapping old id {0} to new id {1}", currentPrim.ID, e.Prim.ID);
								primitiveRemapping[currentPrim.ID] = e.Prim;
							
								_session.GridClient.Objects.DeselectObject(_session.GridClient.Network.CurrentSim, e.Prim.LocalID);
							
		                        // TODO: Is there a way to set all of this at once, and update more ObjectProperties stuff?
		                        _session.GridClient.Objects.SetPosition(e.Simulator, e.Prim.LocalID, currentPosition);
		                        _session.GridClient.Objects.SetTextures(e.Simulator, e.Prim.LocalID, currentPrim.Textures);
		
		                        if (currentPrim.Light != null && currentPrim.Light.Intensity > 0) {
		                            _session.GridClient.Objects.SetLight(e.Simulator, e.Prim.LocalID, currentPrim.Light);
		                        }
		
								if(currentPrim.Flexible != null)
		                        	_session.GridClient.Objects.SetFlexible(e.Simulator, e.Prim.LocalID, currentPrim.Flexible);
		 
		                        if(currentPrim.Sculpt != null && currentPrim.Sculpt.SculptTexture != UUID.Zero) {
		                            _session.GridClient.Objects.SetSculpt(e.Simulator, e.Prim.LocalID, currentPrim.Sculpt);
		                        }
		
		                        if (!String.IsNullOrEmpty(currentPrim.Properties.Name))
		                            _session.GridClient.Objects.SetName(e.Simulator, e.Prim.LocalID, currentPrim.Properties.Name);
		                        if (!String.IsNullOrEmpty(currentPrim.Properties.Description))
		                            _session.GridClient.Objects.SetDescription(e.Simulator, e.Prim.LocalID, currentPrim.Properties.Description);
		
		                        primsCreated.Add(e.Prim);
		                        primDone.Set();
		                    }
		                    break;
		                case ImporterState.Linking:
		                    lock (linkQueue)
		                    {
		                        int index = linkQueue.IndexOf(e.Prim.LocalID);
		                        if (index != -1)
		                        {
		                            linkQueue.RemoveAt(index);
		                            if (linkQueue.Count == 0)
		                                primDone.Set();
		                        }
		                    }
		                    break;
		            }
		        };
#endregion Locally defined delegates			
			
			_session.GridClient.Objects.ObjectUpdate += OnNewPrimCallback;
			
            // HACK: Import the structure just above our head
            // We need a more elaborate solution for importing with relative or absolute offsets
            staleLinkSet.Root.Position = _session.GridClient.Self.SimPosition;
            staleLinkSet.Root.Position.Z += 3.0f;

            // Rez the prims, make sure we crate the prims in the correct sequence
			staleLinkSet.SortChildren();
            foreach(Primitive prim in staleLinkSet.Children)
            {
                currentPrim = prim;
				
				if(prim.ParentID == 0) {
		            state = ImporterState.RezzingParent;
		            currentPosition = staleLinkSet.Root.Position;
				} else {
		            state = ImporterState.RezzingChildren;
	                currentPosition = staleLinkSet.Root.Position + prim.Position;
				}
					
                _session.GridClient.Objects.AddPrim(_session.GridClient.Network.CurrentSim,
				                                    prim.PrimData,
				                                    prim.GroupID,
				                                    currentPosition,
				                                    prim.Scale,
				                                    staleLinkSet.Root.Rotation*prim.Rotation);

                if (!primDone.WaitOne(10000, false))
				{
					// TODO we should cleanup the mess we've created
                    _session.Logger.Error("Rez failed, timed out while creating child prim.");
					return UUID.Zero;
				}
				
                _session.GridClient.Objects.SetPosition(_session.GridClient.Network.CurrentSim,
				                                        primsCreated[primsCreated.Count - 1].LocalID,
				                                        currentPosition);

				if(prim.ParentID == 0)
		            liveLinkSet = new LinkSet(_session, new BackendPrimitive(primsCreated[primsCreated.Count - 1]));
				else
					liveLinkSet.Children.Add(new BackendPrimitive(primsCreated[primsCreated.Count - 1]));
            }

			// Create a list of the local IDs of the newly created prims
			List<uint> primIDs = liveLinkSet.Children.ConvertAll<uint>(p => p.LocalID);
            if(staleLinkSet.Children.Count > 1)		// 1 because we always have the root
            {
				// Allow the sim to settle down, and complete any setup
				// OpenSim likes to complain about:
				//"Aborted linking [a] to [b] as it has yet to finish delinking 
				Thread.Sleep(500);
				
				// Link and set the permissions + rotation
				state = ImporterState.Linking;
                _session.GridClient.Objects.LinkPrims(_session.GridClient.Network.CurrentSim, primIDs);

                if (primDone.WaitOne(1000 * staleLinkSet.Children.Count, false))
                    _session.GridClient.Objects.SetRotation(_session.GridClient.Network.CurrentSim, rootLocalID, staleLinkSet.Root.Rotation);
                else
                    Console.WriteLine("Warning: Failed to link {0} prims", linkQueue.Count);
            }
            else
            {
                _session.GridClient.Objects.SetRotation(_session.GridClient.Network.CurrentSim, rootLocalID, staleLinkSet.Root.Rotation);
            }
            
			// Ensure that we have all the properties of the created prim downloaded
			if(Utilities.RequestObjectProperties(_session, liveLinkSet.Children.ConvertAll<Primitive>(p => p), 250))
			{
				foreach(Primitive livePrim in primitiveRemapping.Values)
				{
					// this is dangrous but it *MUST* work, otherwise an exception is an ok response
					liveLinkSet.Children.Find(p => p.LocalID == livePrim.LocalID).Properties = livePrim.Properties;
					if(livePrim.Properties == null)
						_session.Logger.WarnFormat("Properties does not exist");
				}
			}
			else
				_session.Logger.WarnFormat("Failed to retrieve properties the created prims");

			// Set permissions on newly created prims
            _session.GridClient.Objects.SetPermissions(_session.GridClient.Network.CurrentSim, primIDs,
                PermissionWho.Everyone | PermissionWho.Group | PermissionWho.NextOwner,
                PermissionMask.All, true);
            
            state = ImporterState.Idle;
		
			_session.GridClient.Objects.ObjectUpdate -= OnNewPrimCallback;
			
			// Upload task inventories
			foreach(BackendPrimitive be in staleLinkSet.Children) 
			{
				if(be.Inventory.Count > 0)
				{
					foreach(InventoryItem item in be.Inventory) {
						if(_assetRemapping.ContainsKey(item.AssetUUID))
						{
							InventoryItem newItem = _assetRemapping[item.AssetUUID]; 
							_session.Logger.DebugFormat("Uploading '{0}' into task inventory of '{1}'", newItem.Name, be.Properties!=null?be.Properties.Name:be.ID.ToString());
							_session.GridClient.Inventory.UpdateTaskInventory(primitiveRemapping[be.ID].LocalID, newItem);
							
							Utilities.HardDeleteItem(_session, newItem.UUID);
							_session.Logger.DebugFormat("Removing '{0}' from workfolder", newItem.Name);
						} else {
							_session.Logger.ErrorFormat("Failed to find live item for stale asset {0}", item.AssetUUID);
						}
					}
				}
			}

			// and finally de-rez to the inventory
			UUID newItemID = Utilities.DeRezObject(_session, primsCreated.Find(p => p.LocalID == rootLocalID ), destinationType, destinationFolder);
			_session.Logger.InfoFormat("Upload of {0} succeeded", staleLinkSet.Root.Properties.Name);
			return newItemID;
		}
	}
}
